package cn.mandy.sysadmin.service.impl;

import cn.mandy.sysadmin.component.AuthenticationUser;
import cn.mandy.sysadmin.constant.MandyConstant;
import cn.mandy.sysadmin.dao.MandyRoleMenuDao;
import cn.mandy.sysadmin.dto.MandyAuthority;
import cn.mandy.sysadmin.dto.MandyRegister;
import cn.mandy.sysadmin.dto.MandyUserDetail;
import cn.mandy.sysadmin.mapper.*;
import cn.mandy.sysadmin.model.*;
import cn.mandy.sysadmin.service.MandyCacheService;
import cn.mandy.sysadmin.service.MandyRbacService;
import cn.mandy.sysadmin.util.JwtTokenUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.AntPathMatcher;

import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


/**
 * MandyRbacService, 和 UserDetailsService 的实现类
 * 负责应用程序登录, 权限控制等的数据获取型操作
 * Created by winter zhong on 2020/1/8.
 */

//@Service
@Component("mandyRbacService")
public class MandyRbacServiceImpl implements MandyRbacService, UserDetailsService {
    private static final Logger LOGGER = LoggerFactory.getLogger(cn.mandy.sysadmin.service.impl.MandyRbacServiceImpl.class);

    private static Set basePermission;

    static{
        basePermission = new HashSet();
        basePermission.add("/sysadmin/dict/list");


        basePermission.add("/sysadmin/info");
        basePermission.add("/sysadmin/logout");
        basePermission.add("/sysadmin/role/getAllParameters");

        //加上基本的必须的权限
//        mandyAuthorities.add(new MandyAuthority("/sysadmin/dict/list"));
//        mandyAuthorities.add(new MandyAuthority("/sysadmin/info"));
//        mandyAuthorities.add(new MandyAuthority("/sysadmin/logout"));
//        mandyAuthorities.add(new MandyAuthority("/sysadmin/role/getAllParameters"));
    }

    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Autowired
    private MandyUserMapper mandyUserMapper;

    @Autowired
    private MandyRoleMenuDao mandyRoleMenuDao;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private MandyRoleMapper mandyRoleMapper;

    @Autowired
    private MandyUserRoleMapper mandyUserRoleMapper;

    @Autowired
    private MandyPermissionMapper permissionMapper;

    @Autowired
    private MandyRolePermissionMapper mandyRolePermissionMapper;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private MandyRolePermParamMapper mandyRolePermParamMapper;

    @Autowired
    private MandyPermParamMapper mandyPermParamMapper;

    @Autowired
    private MandyParameterMapper mandyParameterMapper;

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Autowired
    private AuthenticationUser authenticationMmUser;

    @Autowired
    private MandyBlobDataMapper mandyBlobDataMapper;

    @Autowired
    private MandyCacheService mandyCacheService;


    @Override
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        Object principal = authentication.getPrincipal();

        if (principal instanceof MandyUserDetail) { //首先判断先当前用户是否是我们UserDetails对象。
            MandyUserDetail mandyUserDetail = (MandyUserDetail) principal;
            //String userName = mandyUserDetail.getUsername();
            List<MandyAuthority> authorities = (List<MandyAuthority>) mandyUserDetail.getAuthorities();

            // 注意这里不能用equal来判断，因为有些URL是有参数的，所以要用AntPathMatcher来比较
            String requestUri = request.getRequestURI();

            // 如果是BASE权限, 直接放行
            if (basePermission.contains(requestUri)){
                return true;
            }

            String servletPath = request.getServletPath();
            System.out.println("servletPath:" + servletPath);
            String method = request.getMethod();
            for (MandyAuthority url : authorities) {
                StringBuilder cfgUrl = new StringBuilder();
                cfgUrl.append(url.getAuthority());

                if (antPathMatcher.match(cfgUrl.toString(), requestUri)) {
                    Long roleId = mandyUserDetail.getMandyUser().getCurrentRoleId();
                    List<MandyParameter> parameters = mandyCacheService.getParameters(roleId, cfgUrl.toString());
                    if (null != parameters) {
                        mandyUserDetail.setParameters(parameters);
                    }
                    return true;
                }else {
                    //反例,处理PathVariable,见roleDetail;
                    if (method.equals("GET")){
                        cfgUrl.append("/*");
                    }
                    if (antPathMatcher.match(cfgUrl.toString(), requestUri)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }


    @Override
    public int changeCurrentRoleId(Long newRoleId) {
        MandyUserDetail userDetail = authenticationMmUser.getCurrentMmUser();

        //确保新的角色ID是已经授权的
        boolean checkOk = false;
        List<MandyRole> roles = userDetail.getRolesAvailable();
        if (null == roles || roles.isEmpty()){
            return 0;
        }
        for (MandyRole role:  roles) {
            if (0 == role.getId().compareTo(newRoleId) ){
                checkOk = true;
                break;
            }
        }
        if (!checkOk){
            return 0;
        }

        //正式更新用户的当前角色
        MandyUser mandyUser = new MandyUser();
        mandyUser.setId(userDetail.getMandyUser().getId());
        mandyUser.setCurrentRoleId(newRoleId);
        mandyUserMapper.updateByPrimaryKeySelective(mandyUser);

        //刷新缓存
        mandyCacheService.cacheEvictGetMandyUserByName(userDetail.getUsername());
        return 0;
    }



    //不可用Security里的 SecurityContextHolder 等
    //TODO 加缓存, 缓存不适用于功能太复杂的函数中，需要将其细分成多个子函数
    @Override
    //@Cacheable(cacheNames = "loadUserByUsername", unless = "#result.userState =='0' || #result.userState =='1'")
    //@Cacheable(cacheNames = "loadUserByUsername")
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //getMandyUserByName 有缓存
        MandyUser mandyUser = mandyCacheService.getMandyUserByName(username);
        if (null == mandyUser){
            return null;
        }
        //密码被锁定, 离职状态, 拒绝登录!
        //同时注意,当用户状态变化的时候,要删除getMandyUserByName缓存
        if (MandyConstant.UserState.LOCKED.equals(mandyUser.getUserState())
                || MandyConstant.UserIsEnable.DISABLED.equals(mandyUser.getIzEnabled())){
            return null;
        }

        MandyUserDetail myUser = new MandyUserDetail();
        myUser.setMandyUser(mandyUser);

        //取得Roles
        List<MandyRole> rolesAvailable = new ArrayList<>();
        List<MandyRole> rolesUnavailable = new ArrayList<>();
        //getUserRolesByUserId 有缓存
        List<MandyUserRole> userRoles = mandyCacheService.listUserRolesByUserId(mandyUser.getId());

        for (MandyUserRole userRole: userRoles) {
            //对角色的状态进行确认, 支持角色的禁用
            MandyRole role = mandyCacheService.getRoleById(userRole.getRoleId());
            if (MandyConstant.AuditState.CHECKED.equals(userRole.getAuditState())) {
                if (!MandyConstant.AuditState.UNCHECKED.equals(role.getAuditState())) {
                    rolesAvailable.add(role);
                }
            }else if (MandyConstant.AuditState.UNCHECKED.equals(userRole.getAuditState())) {
                if (!MandyConstant.AuditState.UNCHECKED.equals(role.getAuditState())) {
                    rolesUnavailable.add(role);
                }
            }
        }
        myUser.setRolesAvailable(rolesAvailable);
        myUser.setRolesUnavailable(rolesUnavailable);

        List<MandyAuthority> mandyAuthorities = new ArrayList<>() ;
        //TO-DO 要判断用户的状态，不同状态下取授权信息是不同的
        if (! MandyConstant.AuditState.UNCHECKED.equals(myUser.getMandyUser().getAuditState())
                && MandyConstant.UserState.NORMAL.equals(myUser.getMandyUser().getUserState())){
            //有缓存
            mandyAuthorities = mandyCacheService.listMandyAuthorityByRoleId(mandyUser.getCurrentRoleId());
        }else {
            mandyAuthorities = new ArrayList<>() ;
        }

        //新建,待审核状态下的用户, 只能进行注册信息提交
        if (MandyConstant.UserState.INITIAL.equals(myUser.getMandyUser().getUserState()) ||
                MandyConstant.UserState.CHECKING.equals(myUser.getMandyUser().getUserState()) ) {
            mandyAuthorities.add(new MandyAuthority("/sysadmin/register"));
            mandyAuthorities.add(new MandyAuthority("/sysadmin/avatarUpload"));
        }

        //密码重置状态下的用户, 只能更新自己的密码
        if ( MandyConstant.UserState.RESET.equals(myUser.getMandyUser().getUserState())) {
            mandyAuthorities.add(new MandyAuthority("/sysadmin/password"));
        }

        //密码被锁定
        //myUser.getMandyUser().getUserState().equals("3") ||
        //离职状态
        //myUser.getMandyUser().getIzEnabled().equals("0")
        //以上两种状态下的用户, 在登录的时候就应该被拒绝!
        myUser.setAuthorities(mandyAuthorities);

        return myUser;
    }


    @Override
    public String login(String username, String password) {
        String token = null;
        try {
            UserDetails userDetails = this.loadUserByUsername(username);
            if (null == userDetails || ! passwordEncoder.matches(password,userDetails.getPassword())){
                throw new BadCredentialsException("用户名或者密码不正确");
            }

            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
            token = jwtTokenUtil.generateToken(userDetails);
            //updateLoginTimeByUsername(username);
        } catch (AuthenticationException e) {
            LOGGER.warn("登录异常:{}", e.getMessage());
        }
        return token;
    }


    @Override
    public String refreshToken(String oldToken) {
        String token = oldToken.substring(tokenHead.length());
        if (jwtTokenUtil.canRefresh(token)) {
            return jwtTokenUtil.refreshToken(token);
        }
        return null;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int register(MandyRegister registerData) {
        MandyUser user = new MandyUser();
        MandyUserDetail userDetail =  authenticationMmUser.getCurrentMmUser();

        user.setId(userDetail.getMandyUser().getId());
        MandyBlobData mandyBlobData = new MandyBlobData();
        mandyBlobData.setReferenceTable("mandy_user");
        mandyBlobData.setReferenceColumn("avatar");
        mandyBlobData.setReferenceTableId(user.getId());
        mandyBlobData.setRemark("用户头像");
        mandyBlobDataMapper.insert(mandyBlobData);
        try {
            mandyBlobData.setData(registerData.getAvatarData().getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        //mandyBlobDataMapper.updateByPrimaryKeyWithBLOBs(mandyBlobData);
        MandyBlobDataExample mandyBlobDataExample = new MandyBlobDataExample();
        mandyBlobDataExample.createCriteria()
                .andReferenceTableEqualTo("mandy_user")
                .andReferenceTableIdEqualTo(user.getId());
        mandyBlobDataMapper.deleteByExample(mandyBlobDataExample);
        mandyBlobDataMapper.insert(mandyBlobData);

        user.setAvatar(mandyBlobData.getId());
        user.setPassword(passwordEncoder.encode(registerData.getPassword()));
        user.setName(registerData.getName());
        user.setUsername(registerData.getUsername());
        user.setEmail(registerData.getEmail());
        user.setMobile(registerData.getMobile());
        user.setUserState(MandyConstant.UserState.CHECKING);
        mandyUserMapper.updateByPrimaryKeySelective(user);

        //删除缓存
        mandyCacheService.cacheEvictGetMandyUserByName(userDetail.getUsername());
        return 0;
    }


    //TODO  MandyConstant.UserState.CHECKING is better, require audit
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int passwordUpdate(MandyUser paramUser) {
        MandyUser user = new MandyUser();
        MandyUserDetail userDetail =  authenticationMmUser.getCurrentMmUser();
        user.setId(userDetail.getMandyUser().getId());
        user.setPassword(passwordEncoder.encode(paramUser.getPassword()));
        user.setUserState(MandyConstant.UserState.NORMAL);
        mandyUserMapper.updateByPrimaryKeySelective(user);
        mandyCacheService.cacheEvictGetMandyUserByName(userDetail.getUsername());
        return 0;
    }

    @Override
    public String getAvatar(Long blobDataId) {
        if (null == blobDataId){
            return null;
        }
        MandyBlobData mandyBlobData = mandyBlobDataMapper.selectByPrimaryKey(blobDataId);
        if (null == mandyBlobData || null == mandyBlobData.getData() ){
            return null;
        }

        String ret = null;
        try {
            ret = new String(mandyBlobData.getData(), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        return ret;
    }
}
